//
//  IOPack.m
//  RunSketch
//
//  Created by Brian on 9/3/09.
//  Copyright 2009 Tellart. All rights reserved.
//

#import "IOPack.h"


@implementation IOPack

@synthesize inputValues;
@synthesize outputValues;

-(id)init
{
    NSLog(@"*** DO NOT CALL [TCPSocketIOPackTransport init] DIRECTLY. INSTEAD USE initWithHostName:hostName port:port");
    return nil;
}

-(id)initWithTransport:(id <IOPackTransport>)pTransport
{
    if (self = [super init])
    {
        transport = pTransport;
        [transport retain];
        [self allocValues];
        [self allocBuffers];
    }
    return self;
}

-(void)dealloc
{
    [transport release];
    [self deallocValues];
    [self deallocBuffers];
    [super dealloc];
}

/** private methods **/

- (void) allocValues
{
    setValuesRange = NSMakeRange(0, kSketchProtocol_numSetValues * sizeof(IOPACK_OUTPUT_VALUE));
    
    self.inputValues = [NSMutableData dataWithLength:kSketchProtocol_numSetValues];
    [self.inputValues resetBytesInRange:setValuesRange];
    
    lastInputValues = [[NSMutableData dataWithLength:kSketchProtocol_numSetValues] retain];
    
    self.outputValues = [NSMutableData dataWithLength:kSketchProtocol_numSetValues];
    [self.outputValues resetBytesInRange:setValuesRange];
    
    lastOutputValues = [[NSMutableData dataWithLength:kSketchProtocol_numSetValues] retain];
}

- (void) deallocValues
{
    [inputValues release];
    [lastInputValues release];
    [outputValues release];
    [lastOutputValues release];
}

- (void) allocBuffers
{
        //set up buffers
        if ( incomingMessageBuffer == NULL )
        {
            incomingMessageBuffer = (char*) malloc(incomingMessageBufferSize);
            if ( incomingMessageBuffer == NULL )
                [NSException raise:@"allocMessageBuffer malloc failed for incoming buffer" format:@"allocMessageBuffer malloc failed for incoming buffer"];
        }
        if ( outgoingMessageBuffer == NULL )
        {
            outgoingMessageBuffer = (char*) malloc(outgoingMessageBufferSize);
            if ( outgoingMessageBuffer == NULL )
                [NSException raise:@"allocMessageBuffer malloc failed for outgoing buffer" format:@"allocMessageBuffer malloc failed for outgoing buffer"];
        }
    
        //set up parsing stuff
        incomingMessageBufferSize = kSketchProtocol_longestPossibleMessageLength;
        outgoingMessageBufferSize = kSketchProtocol_longestPossibleMessageLength;
        
        flowControl = NO;
        serverFlowControlWaitForSetUntil = 0;
        
        incomingMessageBufferCursor = 0;
        incomingMessageBufferExpectedLength = -1;
        readBufferCursor = 0;
        numLastBytesRead = 0;
}

- (void) deallocBuffers
{
    //deallocate buffers
    if ( incomingMessageBuffer )
    {
        free(incomingMessageBuffer);
        incomingMessageBuffer = NULL;
    }
    if ( outgoingMessageBuffer )
    {
        free(outgoingMessageBuffer);
        outgoingMessageBuffer = NULL;
    }

}


- (void) sendGetMessage
{
    outgoingMessageBuffer[0] = kSketchProtocol_getMessageOpcode;
    if ([transport isConnectedAndReady]) {
        if (![transport writeData:
        [NSData dataWithBytesNoCopy: outgoingMessageBuffer
                length: kSketchProtocol_getMessageLength
                freeWhenDone: NO ]]) {
            //could not write, do something?
        }
    }
}


- (void) sendSetMessage
{
    outgoingMessageBuffer[0] = kSketchProtocol_setMessageOpcode;
    int bufferOffset;
    IOPACK_OUTPUT_VALUE *values = [outputValues mutableBytes];
    int ln = [outputValues length];
    
    //NSLog(@"[IOPack sendSetMessage] --");
    for (int i = 0; i < ln; i++) {
        bufferOffset = kSketchProtocol_messageOffsetOpcode + kSketchProtocol_messageOpcodeLength + (i * kSketchProtocol_bytesPerValue);
        //NSLog(@"[IOPack sendSetMessage] %d: %d -- %d %d", i, values[i], outgoingMessageBuffer[bufferOffset], outgoingMessageBuffer[bufferOffset + 1]);
        outgoingMessageBuffer[bufferOffset] = ((values[i] >> 4) & kSketchProtocol_messageValueMask);
        outgoingMessageBuffer[bufferOffset + 1] = (values[i] & kSketchProtocol_messageValueMask);
    }
    //NSLog(@"[IOPack sendSetMessage] --");
    
    if ([transport isWritable]) [transport writeData:
        [NSData dataWithBytesNoCopy: outgoingMessageBuffer
                length: kSketchProtocol_setMessageLength
                freeWhenDone: NO ]];
}

- (void) processInputBuffer
{
    for (;
            readBufferCursor < numLastBytesRead;
            readBufferCursor++
    ) {
            //NSLog(@"MainViewController.processInputBuffer LOOP readBufferCursor=%i numLastBytesRead=%i", readBufferCursor, numLastBytesRead);
            //there is some data in the buffer


            //cin = (socket.readBuffer)[readBufferCursor];
            cin = transport.readBuffer[readBufferCursor];

            if (cin == kSketchProtocol_setMessageOpcode) {
                    //NSLog(@"GOT SET MESSAGE AT BYTE %i ", i);
                    //found a set message, so reset the message buffer cursor and set the expected message length
                    incomingMessageBufferCursor = 0;
                    incomingMessageBufferExpectedLength = kSketchProtocol_setMessageLength;
            }
                   
            //if there is still space left in the incoming message buffer, add this byte to it and advance the cursor
            if (incomingMessageBufferCursor < incomingMessageBufferExpectedLength) {
                    //copy byte from the socket read buffer into the message buffer
                    (incomingMessageBuffer)[incomingMessageBufferCursor] = cin;
                    incomingMessageBufferCursor++;
            }
           
            //when the incoming set message buffer fills...
            if (incomingMessageBufferCursor == incomingMessageBufferExpectedLength) {
                    //NSLog(@"Message buffer has reached expected length!");
                    //...process it
                    if ((incomingMessageBuffer)[ 0 ] //kSketchProtocol_messageOffsetOpcode
                    == kSketchProtocol_setMessageOpcode ) {
                            serverFlowControlWaitForSetUntil = 0;
                            waitingForSet = NO;
                            [self parseMessageFromBuffer];
                           
                            //update the measured iopack sampling rate
                            now = CFAbsoluteTimeGetCurrent();
                            actualIOPackSamplingRate += (1 / (now - lastIOPackSampleTime));
                            actualIOPackSamplingRate /= 2;
                            lastIOPackSampleTime = now;
                            //NSLog(@"Average sampling rate: %f", actualIOPackSamplingRate);
                           
                    }
                   
                    //...and reset the message buffer
                    incomingMessageBufferCursor = 0;
                    incomingMessageBufferExpectedLength = -1;
                   
                    //if we're not using flow control, stop processing the network buffer until
                    //the next time the timer fires
                    if (flowControl == NO) return;
                   
            } //if message buffer has reached expected length
           
    } //while more data in the socket buffer
}

- (void) parseMessageFromBuffer
{
    if (incomingMessageBuffer[kSketchProtocol_messageOffsetOpcode] == kSketchProtocol_setMessageOpcode) {
        int bufferOffset;
        IOPACK_INPUT_VALUE *values = [self.inputValues mutableBytes];
        int ln = [self.inputValues length];
        
        //NSLog(@"[IOPack parseMessageFromBuffer]: ln = %d --", ln);
        for (int i = 0; i < ln; i++) {
            bufferOffset = kSketchProtocol_messageOffsetOpcode + kSketchProtocol_messageOpcodeLength + (i * kSketchProtocol_bytesPerValue);
            values[i] = ((incomingMessageBuffer[bufferOffset] & kSketchProtocol_messageValueMask) << 4)
                        +
                        (incomingMessageBuffer[bufferOffset + 1] & kSketchProtocol_messageValueMask);
            //NSLog(@"[IOPack parseMessageFromBuffer]: value %d = %d", i, values[i]);
        }
        //NSLog(@"[IOPack parseMessageFromBuffer]: --");
        
    } else {
        NSLog(@"[IOPack parseMessageFromBuffer]: ERROR incoming message from IOPack had invalid opcode");
    }
}

/** public methods **/

-(BOOL)isReady
{
    BOOL r = NO;
    if (transport) {
        r = [transport isConnectedAndReady];
    }
    return r;
}

- (NSString *)deviceAddress
{
    return transport.deviceAddress;
}

- (int)numOutputChannels
{
    return [transport numOutputChannels];
}

- (int)numInputChannels
{
    return [transport numInputChannels];
}

-(void)setHardwareOutputsFromOutputValues
{
    //check if the values have changed since the last time the hardware was set
    if ([outputValues mutableBytes] != [lastOutputValues mutableBytes]) {
        //values are different, so send a set
        [self sendSetMessage];
        
        //then record for next time
        [lastOutputValues replaceBytesInRange:setValuesRange withBytes:[outputValues mutableBytes]];
    }
}

-(BOOL)setInputValuesFromHardwareInputs
{
    BOOL changed = NO;
    
    if (!waitingForSet) {
        [self sendGetMessage];
        waitingForSet = YES;
    }
    
    //update the current time
    now = CFAbsoluteTimeGetCurrent();

    //collect the latest incoming data from the transport
    if (
        [transport isReadable]
        &&
        (
            (numLastBytesRead == 0)
            ||
            (readBufferCursor >= numLastBytesRead)
        )
    ) {
        @try {
            numLastBytesRead = [transport read];
            readBufferCursor = 0;
            //NSLog(@"MainViewController: Read %i bytes", numLastBytesRead);

        }
        @catch (id theException) {
            NSLog(@"Exception while trying to read from transport: %@", theException);
        }
    }
    
    //see if we have received a set message from the transport
    if (numLastBytesRead > 0) [self processInputBuffer];
    
    //if flow control is enabled, send a get message to the transport
    if (flowControl) {
        if (now >= serverFlowControlWaitForSetUntil) {
            [self sendGetMessage];
            serverFlowControlWaitForSetUntil = CFAbsoluteTimeGetCurrent() + kFlowControlWaitForSetTimeout;
        }
    }
    
    //check if the values have changed
    if (!waitingForSet) {
        changed = ([inputValues mutableBytes] != [lastInputValues mutableBytes]);
        
        //record for next time
        if (changed) [lastInputValues replaceBytesInRange:setValuesRange withBytes:[inputValues mutableBytes]];
    }
    
    return changed;
}

-(void)setFlowControl:(BOOL)state
{
    flowControl = state;
}

-(BOOL)getFlowControl
{
    return flowControl;
}

@end
